﻿using Sidvall.Net.Mail;
using Sidvall.Net.Sms;
using Sidvall.Serialization;
using Sidvall.WorkItemManager.Data;
using Sidvall.WorkItemManager.Net.Services.V1;
using System.Linq;
using System.Threading.Tasks;

namespace Sidvall.WorkItemManager.Workflow.WorkItems
{
    public class MonitorWorkItemProvider : WorkItemProviderBase
    {
        #region Public Members

        public const string ResultParameter = "{Result}";

        #region ExecuteAsync

        public override async Task<WorkItemResult> ExecuteAsync()
        {
            WorkItemSensor workItemSensor;

            DeserializeContext();

            var message = new System.Text.StringBuilder();
            var workItemIds = (from o in this.ContextItem.MonitoredWorkItems.Items
                               select o.WorkItemId.Value).ToArray();
            var criteria = new Sidvall.Data.FetchListCriteria<Data.Fields.WorkItemField, Data.Relations.WorkItemRelation>();
            criteria.IncludedEntities = new Sidvall.Data.RelationCollection<Data.Relations.WorkItemRelation>();
            criteria.IncludedEntities.Add(Data.Relations.WorkItemRelation.WorkItemSensors);
            var serverConnection = SystemContextConfig.Current.EntityFacade.ServerConnectionManager.CreateServerConnection(this.ContextItem);
            var workItems = await Sidvall.WorkItemManager.SystemContext.Current.DataPortal.WorkItemManager.GetItemsByPrimaryKeysAsync(serverConnection, workItemIds, criteria, null).ConfigureAwait(false);
            foreach (var workItem in workItems.Items)
            {
                var monitoredWorkItem = (from o in this.ContextItem.MonitoredWorkItems.Items
                                         where o.WorkItemId == workItem.WorkItemId
                                         select o).FirstOrDefault();
                var workItemAlarm = (from o in this.ExecutionContextItem.WorkItemAlarms.Items
                                     where o.WorkItemId == workItem.WorkItemId
                                     select o).FirstOrDefault();
                if (!string.IsNullOrWhiteSpace(monitoredWorkItem.SensorName))
                {
                    workItemSensor = (from o in workItem.WorkItemSensors.Items
                                      where o.Name == monitoredWorkItem.SensorName
                                      select o).FirstOrDefault();
                }
                else
                {
                    workItemSensor = null;
                }
                var alarmIsActive = false;
                var workItemMessage = new System.Text.StringBuilder();

                // Check IdleTime (if process has been idle to long)
                var idleTime = Sidvall.WorkItemManager.SystemContext.Current.EntityFacade.WorkItemManager.GetIdleTime(workItem);
                if ((monitoredWorkItem.MaxIdleTime != null) && (idleTime != null) && (idleTime > monitoredWorkItem.MaxIdleTime))
                {
                    alarmIsActive = true;
                    if ((workItemAlarm == null) || (!IsTimedOut(workItemAlarm.MaxIdleTimeAlarmDate, monitoredWorkItem.MaxIdleTimeAlarmTimeout)))
                    {
                        SetWorkItemMessage(workItemMessage, workItem, monitoredWorkItem, idleTime);
                        if (workItemAlarm == null)
                        {
                            workItemAlarm = new WorkItemAlarm()
                            {
                                WorkItemId = workItem.WorkItemId.Value,
                                WorkItemName = workItem.Name,
                            };
                            this.ExecutionContextItem.WorkItemAlarms.Add(workItemAlarm);
                        }
                        workItemAlarm.MaxIdleTimeAlarmDate = System.DateTime.UtcNow;
                    }
                }

                // Check ExecutionTime (if process has run to long)
                var executionTime = Sidvall.WorkItemManager.SystemContext.Current.EntityFacade.WorkItemManager.GetExecutionTime(workItem);
                if ((monitoredWorkItem.MaxExecutionTime != null) && (executionTime != null) && (executionTime > monitoredWorkItem.MaxExecutionTime))
                {
                    alarmIsActive = true;
                    if ((workItemAlarm == null) || (!IsTimedOut(workItemAlarm.MaxExecutionTimeAlarmDate, monitoredWorkItem.MaxExecutionTimeAlarmTimeout)))
                    {
                        SetWorkItemMessage(workItemMessage, workItem, monitoredWorkItem, executionTime);
                        if (workItemAlarm == null)
                        {
                            workItemAlarm = new WorkItemAlarm()
                            {
                                WorkItemId = workItem.WorkItemId.Value,
                                WorkItemName = workItem.Name,
                            };
                            this.ExecutionContextItem.WorkItemAlarms.Add(workItemAlarm);
                        }
                        workItemAlarm.MaxExecutionTimeAlarmDate = System.DateTime.UtcNow;
                    }
                }

                // Check FailedExecutionCount (if process has failed to many times)
                if ((monitoredWorkItem.MaxFailedExecutionCount != null) && (workItem.FailedExecutionCount > monitoredWorkItem.MaxFailedExecutionCount))
                {
                    alarmIsActive = true;
                    if ((workItemAlarm == null) || (!IsTimedOut(workItemAlarm.MaxFailedExecutionAlarmDate, monitoredWorkItem.MaxFailedExecutionAlarmTimeout)))
                    {
                        SetWorkItemMessage(workItemMessage, workItem, monitoredWorkItem, workItem.FailedExecutionCount);
                        if (workItemAlarm == null)
                        {
                            workItemAlarm = new WorkItemAlarm()
                            {
                                WorkItemId = workItem.WorkItemId.Value,
                                WorkItemName = workItem.Name,
                            };
                            this.ExecutionContextItem.WorkItemAlarms.Add(workItemAlarm);
                        }
                        workItemAlarm.MaxFailedExecutionAlarmDate = System.DateTime.UtcNow;
                    }
                }

                // Check sensor values
                if ((workItemSensor != null) && (!string.IsNullOrWhiteSpace(monitoredWorkItem.SensorValue)))
                {
                    var sensorAlarm = false;
                    switch (monitoredWorkItem.SensorComparisonOperator)
                    {
                        case Sidvall.Data.ComparisonOperator.Equal:
                            if (workItemSensor.Value == monitoredWorkItem.SensorValue)
                                sensorAlarm = true;
                            break;
                        case Sidvall.Data.ComparisonOperator.GreaterThan:
                            if (workItemSensor.Value.ToNullableDouble() > monitoredWorkItem.SensorValue.ToNullableDouble())
                                sensorAlarm = true;
                            break;
                        case Sidvall.Data.ComparisonOperator.GreaterThanOrEqual:
                            if (workItemSensor.Value.ToNullableDouble() >= monitoredWorkItem.SensorValue.ToNullableDouble())
                                sensorAlarm = true;
                            break;
                        case Sidvall.Data.ComparisonOperator.LessThan:
                            if (workItemSensor.Value.ToNullableDouble() < monitoredWorkItem.SensorValue.ToNullableDouble())
                                sensorAlarm = true;
                            break;
                        case Sidvall.Data.ComparisonOperator.LessThanOrEqual:
                            if (workItemSensor.Value.ToNullableDouble() <= monitoredWorkItem.SensorValue.ToNullableDouble())
                                sensorAlarm = true;
                            break;
                        case Sidvall.Data.ComparisonOperator.NotEqual:
                            if (workItemSensor.Value != monitoredWorkItem.SensorValue)
                                sensorAlarm = true;
                            break;
                        case Sidvall.Data.ComparisonOperator.StartsWith:
                            if (workItemSensor.Value.StartsWith(monitoredWorkItem.SensorValue))
                                sensorAlarm = true;
                            break;
                        default:
                            break;
                    }
                    if (sensorAlarm)
                    {
                        alarmIsActive = true;
                        if ((workItemAlarm == null) || (!IsTimedOut(workItemAlarm.SensorAlarmDate, monitoredWorkItem.SensorAlarmTimeout)))
                        {
                            SetWorkItemMessage(workItemMessage, workItem, monitoredWorkItem, workItemSensor);
                            if (workItemAlarm == null)
                            {
                                workItemAlarm = new WorkItemAlarm()
                                {
                                    WorkItemId = workItem.WorkItemId.Value,
                                    WorkItemName = workItem.Name,
                                };
                                this.ExecutionContextItem.WorkItemAlarms.Add(workItemAlarm);
                            }
                            workItemAlarm.SensorAlarmDate = System.DateTime.UtcNow;
                        }
                    }
                }

                // Check if alarm should be removed
                if ((!alarmIsActive) && (workItemAlarm != null))
                {
                    this.ExecutionContextItem.WorkItemAlarms.Remove(workItemAlarm);
                }

                if (message.Length > 0)
                {
                    message.AppendLine();
                    message.AppendLine();
                }
                message.Append(workItemMessage);
            }

            // Update sensors
            this.SensorValues.UpdateSensor("AlarmCount", this.ExecutionContextItem.WorkItemAlarms.Items.Count.ToString(), System.DateTime.UtcNow);
            await UpdateSensorsAsync().ConfigureAwait(false);

            if (message.Length > 0)
            {
                // Send email
                if (!string.IsNullOrWhiteSpace(this.ContextItem.SmtpServer))
                {
                    var mailMessage = new MailMessage()
                    {
                        RecipientAddresses = this.ContextItem.RecipientAddresses,
                        ConnectionString = this.ContextItem.SmtpServer,
                        Subject = this.ContextItem.Subject,
                        FromAddress = this.ContextItem.FromAddress,
                        BccRecipientAddresses = this.ContextItem.BccRecipientAddresses,
                        CCRecipientAddresses = this.ContextItem.CCRecipientAddresses,
                    };
                    if (!string.IsNullOrWhiteSpace(this.ContextItem.Body))
                    {
                        mailMessage.Body = this.ContextItem.Body.Replace(ResultParameter, message.ToString());
                        mailMessage.IsBodyHtml = this.ContextItem.IsBodyHtml;
                    }
                    else
                    {
                        mailMessage.Body = message.ToString();
                        mailMessage.IsBodyHtml = false;
                    }
                    await Sidvall.SystemContext.Current.SmtpClientManager.SendAsync(mailMessage).ConfigureAwait(false);
                }

                // Send SMS
                if (!string.IsNullOrWhiteSpace(this.ContextItem.SmsWebAddress))
                {
                    var smsMessage = new SmsMessage()
                    {
                        ConnectionString = this.ContextItem.SmsWebAddress,
                        MobileNumber = this.ContextItem.MobileNumber,
                        Flash = false,
                    };
                    if (!string.IsNullOrWhiteSpace(this.ContextItem.SmsBody))
                    {
                        smsMessage.Body = this.ContextItem.SmsBody.Replace(ResultParameter, message.ToString());
                    }
                    else
                    {
                        smsMessage.Body = message.ToString();
                    }
                    await Sidvall.SystemContext.Current.SmsClientManager.SendAsync(smsMessage).ConfigureAwait(false);
                }
            }
            SerializeContext();

            return new WorkItemResult(WorkItemResultType.Ok, message.ToString());
        }

        #endregion
        #region GetSensorsAsync

        public override Task<SensorCollection> GetSensorsAsync()
        {
            SensorCollection sensors;

            sensors = new SensorCollection();
            sensors.Add("AlarmCount", "Number of alarms");

            return Task.FromResult(sensors);
        }

        #endregion

        #endregion
        #region Private Members

        private MonitorContext ContextItem { get; set; }
        private MonitorExecutionContext ExecutionContextItem { get; set; }

        #region DeserializeContext

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void DeserializeContext()
        {
            this.ContextItem = Sidvall.Serialization.SerializerManager.Current.Deserialize<MonitorContext>(this.Context);
            this.ExecutionContextItem = null;
            try
            {
                this.ExecutionContextItem = Sidvall.Serialization.SerializerManager.Current.Deserialize<MonitorExecutionContext>(this.ExecutionContext);
            }
            catch
            {
                this.ExecutionContextItem = null;
            }
            if (this.ExecutionContextItem == null)
                this.ExecutionContextItem = new MonitorExecutionContext();
            if (this.ExecutionContextItem.WorkItemAlarms == null)
                this.ExecutionContextItem.WorkItemAlarms = new WorkItemAlarmCollectionItem();
        }

        #endregion
        #region IsTimedOut

        private static bool IsTimedOut(System.DateTime? alarmDate, System.TimeSpan? alarmTimeout)
        {
            System.DateTime endDate;

            if (alarmDate == null)
                return false;
            if (alarmTimeout == null)
                return false;
            endDate = alarmDate.Value.Add(alarmTimeout.Value);
            if (System.DateTime.UtcNow > endDate)
                return false;
            return true;
        }

        #endregion
        #region SerializeContext

        private void SerializeContext()
        {
            this.ExecutionContext = Sidvall.Serialization.SerializerManager.Current.SerializeObject(this.ExecutionContextItem);
        }

        #endregion
        #region SetWorkItemMessage

        private static void SetWorkItemMessage(System.Text.StringBuilder workItemMessage, IWorkItem workItem)
        {
            if (workItemMessage.Length == 0)
                workItemMessage.AppendLine("Work item: " + workItem.Name + " (" + workItem.WorkItemId.ToString() + ")");
        }
        private static void SetWorkItemMessage(System.Text.StringBuilder workItemMessage, IWorkItem workItem, MonitoredWorkItem monitoredWorkItem, int failedExecutionCount)
        {
            SetWorkItemMessage(workItemMessage, workItem);
            workItemMessage.AppendLine("Max number of failed executions (" + monitoredWorkItem.MaxFailedExecutionCount.ToString() + ") exceeded. Number of failed executions: " + failedExecutionCount.ToString());
        }
        private static void SetWorkItemMessage(System.Text.StringBuilder workItemMessage, IWorkItem workItem, MonitoredWorkItem monitoredWorkItem, System.TimeSpan? executionTime)
        {
            SetWorkItemMessage(workItemMessage, workItem);
            workItemMessage.AppendLine("Max execution time (" + monitoredWorkItem.MaxExecutionTime.ToString() + ") exceeded. Execution time: " + executionTime.ToString());
        }
        private static void SetWorkItemMessage(System.Text.StringBuilder workItemMessage, IWorkItem workItem, MonitoredWorkItem monitoredWorkItem, WorkItemSensor workItemSensor)
        {
            SetWorkItemMessage(workItemMessage, workItem);
            workItemMessage.AppendLine("Sensor " + workItemSensor.Name + ": " + monitoredWorkItem.SensorComparisonOperator.ToString() + " to " + monitoredWorkItem.SensorValue + ". Value: " + workItemSensor.Value + " (" + workItemSensor.ValueTimestamp.ToString() + ")");
        }

        #endregion

        #endregion
    }
}
